//
// $Id$

package zachg.survival;

import java.awt.Color;
import java.util.ArrayList;

import javax.annotation.Generated;

import com.threerings.io.SimpleStreamableObject;
import com.threerings.parlor.game.data.GameObject;
import com.threerings.parlor.turn.data.TurnGameObject;
import com.threerings.presents.dobj.DSet;
import com.threerings.util.Name;

/**
 * Maintains the shared state of the game.
 */
public class SurvivalObject extends GameObject
implements TurnGameObject
{
    // AUTO-GENERATED: FIELDS START
    /** The field name of the <code>turnHolder</code> field. */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public static final String TURN_HOLDER = "turnHolder";

    /** The field name of the <code>currentTurn</code> field. */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public static final String CURRENT_TURN = "currentTurn";

    /** The field name of the <code>totalGameTurns</code> field. */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public static final String TOTAL_GAME_TURNS = "totalGameTurns";

    /** The field name of the <code>_mineLocations</code> field. */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public static final String _MINE_LOCATIONS = "_mineLocations";

    /** The field name of the <code>NotifyClients</code> field. */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public static final String NOTIFY_CLIENTS = "NotifyClients";

    /** The field name of the <code>gameCharacters</code> field. */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public static final String GAME_CHARACTERS = "gameCharacters";

    /** The field name of the <code>matchPraise</code> field. */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public static final String MATCH_PRAISE = "matchPraise";
    // AUTO-GENERATED: FIELDS END
    
    /** The Constant CURRENT_PLAYER_ID. */
    public static final int PLAYER_ID = 1;
    
    /** The Constant CURRENT_ENEMY_ID. */
    public static final int ENEMY_ID = 0;

    /** The Constant GAME_SIZE. */
    public static final int GAME_SIZE = 40;
    
    public static final String CREEPER_NAME = "Creeper";
    public static final String PLACEHOLDER_NAME = "TempCharacter";
    
    public static final Color[] PlayerColors = { Color.yellow, Color.blue, Color.cyan,Color.magenta,Color.pink,Color.orange,Color.white,Color.black};
    
    /** The username of the current turn holder or null. NOTE: Assumed to be unique for each player*/
    public Name turnHolder;
    
    /** The current turn. */
    public int currentTurn;
    
    /** The total game turns. */
    public int totalGameTurns;
    
    /** The _mine locations. The desired array holds Point objects, but a problem was encountered when doing that.
     * Instead, I use an array of ints, with x and y values alternating
     * NOTE: 	Retrospective moment, if I had of made a class that wraps a Point and extends SimpleStreamableObject,
     * 			I could have made that work. It would have been a bit cleaner, but in the end it's not a biggie. 
     * */
    public int[] _mineLocations;
    
    /** This is actually never used directly. I needed to send a dummy event
     *  through to notify all clients of a change on the server, but the only 
     *  way for me to do that is to trigger a dummy event. I basically made 
     *  that dummy event change this value for no reason. I didn't create a 
     *  new event and add that to the system because I didn't look deep enough 
     *  into how that mechanism works. */
    public int NotifyClients = 0;
    
    /** Holds the game characters, so basically all the player data. */
    public DSet<GameCharacter> gameCharacters = new DSet<GameCharacter>(); 
    
    /** Contains messages about events that occurred in the game.*/
    public MatchPraise matchPraise = null;    
    
    /** Used to assign ids to game characters. */
    protected transient int _nextCharacterId;        
    
    // from interface TurnGameObject
    public String getTurnHolderFieldName ()
    {
        return TURN_HOLDER;
    }

    // from interface TurnGameObject
    public Name getTurnHolder ()
    {
        return turnHolder;
    }

    // from interface TurnGameObject
    public Name[] getPlayers ()
    {
        return players;
    }
    
    /**
     * The Class GameCharacter. This holds all information about a player's stats.
     */
    @SuppressWarnings("rawtypes")
    public static class GameCharacter implements DSet.Entry, Comparable
    {
        
        /** Specific ID for character, no two characters have same ID */
        public int characterId;
        
        /** ID of the player that controls this character */
        public Name playerOwner;
        
        /** Defines if the owner is human or AI now. Int instead of bool to keep it flexible*/
        public int ownerType;
        public int currentPoints = 0;
        public int currentDamage = 1;
        public int killStreak = 0;
        public int deaths = 0;
        public int currentHealth = 5;
        public int x, y;
        
        /** Needed for the GameCharacterSprite to set color properly  */
        public int playerIndex;
        public boolean isCurrentPlayer;
        public boolean isControllable;
        
        
        public Comparable<Integer> getKey () {
            return characterId;
        }
        
        public int compareTo(Object objectToCompare)
        {
        	if(objectToCompare instanceof GameCharacter){
        		GameCharacter gameObj = (GameCharacter)objectToCompare;
        		if( gameObj.characterId == characterId ){
        			return 0;
        		} else if( gameObj.characterId > characterId ){
        			return 1;
        		} else if( gameObj.characterId < characterId ){
        			return -1;
        		}
        	}
        	return -1;
        }
    }
    
    public static class MatchPraise extends SimpleStreamableObject {
        private transient int id = 1;
        public int praiseID;
        public int score;
        public int row;
        public int col;
        public String text;
        
        public MatchPraise() {}
        
        public MatchPraise(int score, int row, int col, String text) {
            this.praiseID = id;
            id++;
            this.score = score;
            this.row = row;
            this.col = col;
            this.text = text;
        }
    }
    
    /**
     * Places the supplied game character onto the board, first assigning it a unique
     * id.
     */
    public void placeGameCharacter (GameCharacter gameCharacter)
    {
    	// assign this game character a new unique id
    	gameCharacter.characterId = ++_nextCharacterId;
    	Log.info("placeGameCharacter:"+_nextCharacterId);
    	
        // add this new game character to the set
        addToGameCharacters(gameCharacter);
    }
  
    /**
     * Gets the character at a location.
     *
     * @param x the x
     * @param y the y
     * @return the character at location
     */
    public SurvivalObject.GameCharacter getCharacterAtLocation(int x, int y)
    {
    	for (SurvivalObject.GameCharacter gameCharacter : gameCharacters) {
    		if(gameCharacter.x == x && gameCharacter.y == y){
    			return gameCharacter;
    		}
    	}    	
    	return null;
    }
    
    /**
     * Gets the healthiest character. 
     * NOTE: 	If two characters have the same value for most health, 
     * 			this will take the last created character. 
     * TODO: Make method pick random character in situation where 2 or more characters have the same value for most health. 
     *
     * @return the healthiest character
     */
    public SurvivalObject.GameCharacter getHealthiestCharacter()
    {
    	int mostHealth = 0;
    	SurvivalObject.GameCharacter currentCharacterWithMostHealth = null;
    	for (SurvivalObject.GameCharacter gameCharacter : gameCharacters) {
    		if(gameCharacter.currentHealth > mostHealth){
    			mostHealth = gameCharacter.currentHealth;
    			currentCharacterWithMostHealth = gameCharacter;
    		}
    	}
    	return currentCharacterWithMostHealth;
    }
    
    /**
     * Gets the game character at an index from gameCharacters array.
     *
     * @param index the index
     * @return the game character from index
     */
    public SurvivalObject.GameCharacter getGameCharacterFromIndex(int index)
    {
    	if(index == -1){
    		return null;
    	}
    	Name desiredPlayerOwner = players[index];
    	for (SurvivalObject.GameCharacter gameCharacter : gameCharacters) {
    		if (gameCharacter.playerOwner.equals(desiredPlayerOwner)){
    			return gameCharacter;
    		}
    	}
    	return null;
    }    
    
    /**
     * Gets the player game character with the same name 
     * 
     * @param playerId the player id
     * @return the current player game character
     */
    public SurvivalObject.GameCharacter getGameCharacterFromName(Name playerId)
    {
    	if( playerId.equals(SurvivalObject.CREEPER_NAME) ){
    		return null;
    	}
    	for (SurvivalObject.GameCharacter gameCharacter : gameCharacters) {
    		if( gameCharacter.playerOwner.equals(playerId) ){
    			return gameCharacter;
    		}
    	}
    	return null;
    }    
    
    /**
     * Sends a dummy event that will notify all clients and force them to show whatever message is held in matchPraise 
     */
    public void showMessageInClients()
    {
    	requestAttributeChange(NOTIFY_CLIENTS,1,1);
    }
    
    public ArrayList<SurvivalObject.GameCharacter> determinePlayersWithMostPoints()
    {
    	ArrayList<SurvivalObject.GameCharacter> currentCharacterWithMostPoints = new ArrayList<SurvivalObject.GameCharacter>();
    	for (SurvivalObject.GameCharacter gameCharacter : gameCharacters) {
    		//check in currentCharacterWithMostPoints to see if this character 
    		//has more points than any in there
    		//we can simply check the first element because all characters in 
    		//array should have the same number of points.
    		if(currentCharacterWithMostPoints.size() == 0){
    			currentCharacterWithMostPoints.add(gameCharacter);
    		} else {
    			if(currentCharacterWithMostPoints.get(0).currentPoints == gameCharacter.currentPoints){
    				//if we've got the same number of points, add at end of array
    				currentCharacterWithMostPoints.add(gameCharacter); 
    			} else if(currentCharacterWithMostPoints.get(0).currentPoints < gameCharacter.currentPoints){
    				currentCharacterWithMostPoints.clear();
    				currentCharacterWithMostPoints.add(gameCharacter);
    			}
    		}
    	}
    	return currentCharacterWithMostPoints;	
    }
    
    // AUTO-GENERATED: METHODS START
    /**
     * Requests that the <code>turnHolder</code> field be set to the
     * specified value. The local value will be updated immediately and an
     * event will be propagated through the system to notify all listeners
     * that the attribute did change. Proxied copies of this object (on
     * clients) will apply the value change when they received the
     * attribute changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void setTurnHolder (Name value)
    {
        Name ovalue = this.turnHolder;
        requestAttributeChange(
            TURN_HOLDER, value, ovalue);
        this.turnHolder = value;
    }

    /**
     * Requests that the <code>currentTurn</code> field be set to the
     * specified value. The local value will be updated immediately and an
     * event will be propagated through the system to notify all listeners
     * that the attribute did change. Proxied copies of this object (on
     * clients) will apply the value change when they received the
     * attribute changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void setCurrentTurn (int value)
    {
        int ovalue = this.currentTurn;
        requestAttributeChange(
            CURRENT_TURN, Integer.valueOf(value), Integer.valueOf(ovalue));
        this.currentTurn = value;
    }

    /**
     * Requests that the <code>totalGameTurns</code> field be set to the
     * specified value. The local value will be updated immediately and an
     * event will be propagated through the system to notify all listeners
     * that the attribute did change. Proxied copies of this object (on
     * clients) will apply the value change when they received the
     * attribute changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void setTotalGameTurns (int value)
    {
        int ovalue = this.totalGameTurns;
        requestAttributeChange(
            TOTAL_GAME_TURNS, Integer.valueOf(value), Integer.valueOf(ovalue));
        this.totalGameTurns = value;
    }

    /**
     * Requests that the <code>_mineLocations</code> field be set to the
     * specified value. The local value will be updated immediately and an
     * event will be propagated through the system to notify all listeners
     * that the attribute did change. Proxied copies of this object (on
     * clients) will apply the value change when they received the
     * attribute changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void set_mineLocations (int[] value)
    {
        int[] ovalue = this._mineLocations;
        requestAttributeChange(
            _MINE_LOCATIONS, value, ovalue);
        this._mineLocations = (value == null) ? null : value.clone();
    }

    /**
     * Requests that the <code>index</code>th element of
     * <code>_mineLocations</code> field be set to the specified value.
     * The local value will be updated immediately and an event will be
     * propagated through the system to notify all listeners that the
     * attribute did change. Proxied copies of this object (on clients)
     * will apply the value change when they received the attribute
     * changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void set_mineLocationsAt (int value, int index)
    {
        int ovalue = this._mineLocations[index];
        requestElementUpdate(
            _MINE_LOCATIONS, index, Integer.valueOf(value), Integer.valueOf(ovalue));
        this._mineLocations[index] = value;
    }

    /**
     * Requests that the <code>NotifyClients</code> field be set to the
     * specified value. The local value will be updated immediately and an
     * event will be propagated through the system to notify all listeners
     * that the attribute did change. Proxied copies of this object (on
     * clients) will apply the value change when they received the
     * attribute changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void setNotifyClients (int value)
    {
        int ovalue = this.NotifyClients;
        requestAttributeChange(
            NOTIFY_CLIENTS, Integer.valueOf(value), Integer.valueOf(ovalue));
        this.NotifyClients = value;
    }

    /**
     * Requests that the specified entry be added to the
     * <code>gameCharacters</code> set. The set will not change until the event is
     * actually propagated through the system.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void addToGameCharacters (SurvivalObject.GameCharacter elem)
    {
        requestEntryAdd(GAME_CHARACTERS, gameCharacters, elem);
    }

    /**
     * Requests that the entry matching the supplied key be removed from
     * the <code>gameCharacters</code> set. The set will not change until the
     * event is actually propagated through the system.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void removeFromGameCharacters (Comparable<?> key)
    {
        requestEntryRemove(GAME_CHARACTERS, gameCharacters, key);
    }

    /**
     * Requests that the specified entry be updated in the
     * <code>gameCharacters</code> set. The set will not change until the event is
     * actually propagated through the system.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void updateGameCharacters (SurvivalObject.GameCharacter elem)
    {
        requestEntryUpdate(GAME_CHARACTERS, gameCharacters, elem);
    }

    /**
     * Requests that the <code>gameCharacters</code> field be set to the
     * specified value. Generally one only adds, updates and removes
     * entries of a distributed set, but certain situations call for a
     * complete replacement of the set value. The local value will be
     * updated immediately and an event will be propagated through the
     * system to notify all listeners that the attribute did
     * change. Proxied copies of this object (on clients) will apply the
     * value change when they received the attribute changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void setGameCharacters (DSet<SurvivalObject.GameCharacter> value)
    {
        requestAttributeChange(GAME_CHARACTERS, value, this.gameCharacters);
        DSet<SurvivalObject.GameCharacter> clone = (value == null) ? null : value.clone();
        this.gameCharacters = clone;
    }

    /**
     * Requests that the <code>matchPraise</code> field be set to the
     * specified value. The local value will be updated immediately and an
     * event will be propagated through the system to notify all listeners
     * that the attribute did change. Proxied copies of this object (on
     * clients) will apply the value change when they received the
     * attribute changed notification.
     */
    @Generated(value={"com.threerings.presents.tools.GenDObjectTask"})
    public void setMatchPraise (SurvivalObject.MatchPraise value)
    {
        SurvivalObject.MatchPraise ovalue = this.matchPraise;
        requestAttributeChange(
            MATCH_PRAISE, value, ovalue);
        this.matchPraise = value;
    }
    // AUTO-GENERATED: METHODS END
}
